﻿using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using FirstFloor.Documents.Annotations.Storage;
using FirstFloor.Documents.Presentation.Annotations;

namespace FirstFloor.Documents.Presentation.Controls
{
    /// <summary>
    /// Represents a draggable sticky note visual.
    /// </summary>
    [TemplatePart(Name="Delete", Type=typeof(FrameworkElement))]
    public class StickyNoteControl
        : Control
    {
        /// <summary>
        /// Identifies the StickyNote dependency property.
        /// </summary>
        public static readonly DependencyProperty StickyNoteProperty = DependencyProperty.Register("StickyNote", typeof(StickyNote), typeof(StickyNoteControl), new PropertyMetadata(OnStickyNoteChanged));
        /// <summary>
        /// Identifies the AnnotationStore dependency property.
        /// </summary>
        public static readonly DependencyProperty AnnotationStoreProperty = DependencyProperty.Register("AnnotationStore", typeof(AnnotationStore), typeof(StickyNoteControl), null);

        private Point? dragMouseOffset;
        private FrameworkElement delete;

        /// <summary>
        /// Initializes a new instance of the <see cref="StickyNoteControl"/> class.
        /// </summary>
        public StickyNoteControl()
        {
            this.DefaultStyleKey = typeof(StickyNoteControl);
        }

        private static void OnStickyNoteChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            // position control according to sticky note settings
            var ctrl = (StickyNoteControl)o;
            var stickyNote = (StickyNote)e.NewValue;
            if (stickyNote != null) {
                Canvas.SetLeft(ctrl, stickyNote.Left);
                Canvas.SetTop(ctrl, stickyNote.Top);
            }
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>. In simplest terms, this means the method is called just before a UI element displays in an application. For more information, see Remarks.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (this.delete != null) {
                // detach event handler
                this.delete.MouseLeftButtonDown -= delete_MouseLeftButtonDown;
            }

            this.delete = GetTemplateChild("Delete") as FrameworkElement;
            if (this.delete != null) {
                this.delete.MouseLeftButtonDown += delete_MouseLeftButtonDown;
            }
        }

        private void delete_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // remove sticky note visual
            var parent = (Canvas)this.Parent;
            parent.Children.Remove(this);

            // remove the sticky note from the store
            if (this.AnnotationStore != null) {
                this.AnnotationStore.Remove(this.StickyNote);
            }

            // mark the mouse event as handled
            e.Handled = true;
        }

        /// <summary>
        /// Gets or sets the sticky note.
        /// </summary>
        /// <value>The sticky note.</value>
        public StickyNote StickyNote
        {
            get { return (StickyNote)GetValue(StickyNoteProperty); }
            set { SetValue(StickyNoteProperty, value); }
        }

        /// <summary>
        /// Gets or sets the annotation store.
        /// </summary>
        /// <value>The annotation store.</value>
        public AnnotationStore AnnotationStore
        {
            get { return (AnnotationStore)GetValue(AnnotationStoreProperty); }
            set { SetValue(AnnotationStoreProperty, value); }
        }
        
        private void StopDragOperation()
        {
            ReleaseMouseCapture();
            this.dragMouseOffset = null;

            // update sticky note left/top
            if (this.StickyNote != null) {
                this.AnnotationStore.BeginEdit();
                this.StickyNote.Left = Canvas.GetLeft(this);
                this.StickyNote.Top = Canvas.GetTop(this);
                this.AnnotationStore.EndEdit();
            }
        }

        /// <summary>
        /// Called before the <see cref="E:System.Windows.UIElement.MouseLeftButtonDown"/> event occurs.
        /// </summary>
        /// <param name="e">The data for the event.</param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            this.dragMouseOffset = e.GetPosition(this);

            e.Handled = true;

            // make the dragged control on top
            var parent = (Canvas)this.Parent;
            parent.Children.Remove(this);
            parent.Children.Add(this);

            CaptureMouse();
        }

        /// <summary>
        /// Called before the <see cref="E:System.Windows.UIElement.MouseLeftButtonUp"/> event occurs.
        /// </summary>
        /// <param name="e">The data for the event.</param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);

            StopDragOperation();
        }

        /// <summary>
        /// Called before the <see cref="E:System.Windows.UIElement.LostMouseCapture"/> event occurs to provide handling for the event in a derived class without attaching a delegate.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Input.MouseEventArgs"/> that contains the event data.</param>
        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);

            StopDragOperation();
        }

        /// <summary>
        /// Called before the <see cref="E:System.Windows.UIElement.MouseMove"/> event occurs.
        /// </summary>
        /// <param name="e">The data for the event.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (this.dragMouseOffset != null) {
                var position = e.GetPosition((UIElement)this.Parent);

                Canvas.SetLeft(this, position.X - this.dragMouseOffset.Value.X);
                Canvas.SetTop(this, position.Y - this.dragMouseOffset.Value.Y);
            }
        }
    }
}
